/*
 * Copyright (c) Thorben Linneweber and others
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

using SoftFloat;

namespace Jitter2.LinearMath
{
	
	/// <summary>
	/// Represents an axis-aligned bounding box (AABB), a rectangular bounding box whose edges are parallel to the coordinate axes.
	/// </summary>
	public struct JBBox
	{
	    public static sfloat Epsilon = 1e-12f;
	
	    public enum ContainmentType
	    {
	        Disjoint,
	        Contains,
	        Intersects
	    }
	
	    public NVector3 Min;
	    public NVector3 Max;
	
	    public static readonly JBBox LargeBox;
	
	    public static readonly JBBox SmallBox;
	
	    public override string ToString()
	    {
	        return $"min: {Min} max: {Max}";
	    }
	
	    static JBBox()
	    {
	        LargeBox.Min = new NVector3(sfloat.MinValue);
	        LargeBox.Max = new NVector3(sfloat.MaxValue);
	        SmallBox.Min = new NVector3(sfloat.MaxValue);
	        SmallBox.Max = new NVector3(sfloat.MinValue);
	    }
	
	    public JBBox(NVector3 min, NVector3 max)
	    {
	        Min = min;
	        Max = max;
	    }
	
	    internal void InverseTransform(ref NVector3 position, ref JMatrix orientation)
	    {
	        NVector3.Subtract(Max, position, out Max);
	        NVector3.Subtract(Min, position, out Min);
	
	        NVector3.Add(Max, Min, out NVector3 center);
	        center.X *= (sfloat)0.5f;
	        center.Y *= (sfloat)0.5f;
	        center.Z *= (sfloat)0.5f;
	
	        NVector3.Subtract(Max, Min, out NVector3 halfExtents);
	        halfExtents.X *= (sfloat)0.5f;
	        halfExtents.Y *= (sfloat)0.5f;
	        halfExtents.Z *= (sfloat)0.5f;
	
	        NVector3.TransposedTransform(center, orientation, out center);
	
	        JMatrix.Absolute(orientation, out JMatrix abs);
	        NVector3.TransposedTransform(halfExtents, abs, out halfExtents);
	
	        NVector3.Add(center, halfExtents, out Max);
	        NVector3.Subtract(center, halfExtents, out Min);
	    }
	
	    public void Transform(ref JMatrix orientation)
	    {
	        NVector3 halfExtents = (sfloat)0.5f * (Max - Min);
	        NVector3 center = (sfloat)0.5f * (Max + Min);
	
	        NVector3.Transform(center, orientation, out center);
	
	        JMatrix.Absolute(orientation, out var abs);
	        NVector3.Transform(halfExtents, abs, out halfExtents);
	
	        Max = center + halfExtents;
	        Min = center - halfExtents;
	    }
	
	    private bool Intersect1D(sfloat start, sfloat dir, sfloat min, sfloat max,
	        ref sfloat enter, ref sfloat exit)
	    {
	        if (dir * dir < Epsilon * Epsilon) return start >= min && start <= max;
	
	        sfloat t0 = (min - start) / dir;
	        sfloat t1 = (max - start) / dir;
	
	        if (t0 > t1)
	        {
	            (t0, t1) = (t1, t0);
	        }
	
	        if (t0 > exit || t1 < enter) return false;
	
	        if (t0 > enter) enter = t0;
	        if (t1 < exit) exit = t1;
	        return true;
	    }
	
	    public bool SegmentIntersect(in NVector3 origin, in NVector3 direction)
	    {
	        sfloat enter = (sfloat)0.0f, exit = (sfloat)1.0f;
	
	        if (!Intersect1D(origin.X, direction.X, Min.X, Max.X, ref enter, ref exit))
	            return false;
	
	        if (!Intersect1D(origin.Y, direction.Y, Min.Y, Max.Y, ref enter, ref exit))
	            return false;
	
	        if (!Intersect1D(origin.Z, direction.Z, Min.Z, Max.Z, ref enter, ref exit))
	            return false;
	
	        return true;
	    }
	
	    public bool RayIntersect(in NVector3 origin, in NVector3 direction)
	    {
	        sfloat enter = (sfloat)0.0f, exit = sfloat.MaxValue;
	
	        if (!Intersect1D(origin.X, direction.X, Min.X, Max.X, ref enter, ref exit))
	            return false;
	
	        if (!Intersect1D(origin.Y, direction.Y, Min.Y, Max.Y, ref enter, ref exit))
	            return false;
	
	        if (!Intersect1D(origin.Z, direction.Z, Min.Z, Max.Z, ref enter, ref exit))
	            return false;
	
	        return true;
	    }
	
	    public bool RayIntersect(in NVector3 origin, in NVector3 direction, out sfloat enter)
	    {
	        enter = (sfloat)0.0f;
	        sfloat exit = sfloat.MaxValue;
	
	        if (!Intersect1D(origin.X, direction.X, Min.X, Max.X, ref enter, ref exit))
	            return false;
	
	        if (!Intersect1D(origin.Y, direction.Y, Min.Y, Max.Y, ref enter, ref exit))
	            return false;
	
	        if (!Intersect1D(origin.Z, direction.Z, Min.Z, Max.Z, ref enter, ref exit))
	            return false;
	
	        return true;
	    }
	
	    public ContainmentType Contains(in NVector3 point)
	    {
	        return Min.X <= point.X && point.X <= Max.X &&
	               Min.Y <= point.Y && point.Y <= Max.Y &&
	               Min.Z <= point.Z && point.Z <= Max.Z
	            ? ContainmentType.Contains
	            : ContainmentType.Disjoint;
	    }
	
	    public void GetCorners(NVector3[] corners)
	    {
	        corners[0].Set(Min.X, Max.Y, Max.Z);
	        corners[1].Set(Max.X, Max.Y, Max.Z);
	        corners[2].Set(Max.X, Min.Y, Max.Z);
	        corners[3].Set(Min.X, Min.Y, Max.Z);
	        corners[4].Set(Min.X, Max.Y, Min.Z);
	        corners[5].Set(Max.X, Max.Y, Min.Z);
	        corners[6].Set(Max.X, Min.Y, Min.Z);
	        corners[7].Set(Min.X, Min.Y, Min.Z);
	    }
	
	    public void AddPoint(in NVector3 point)
	    {
	        NVector3.Max(Max, point, out Max);
	        NVector3.Min(Min, point, out Min);
	    }
	
	    public static JBBox CreateFromPoints(NVector3[] points)
	    {
	        NVector3 vector3 = new NVector3(sfloat.MaxValue);
	        NVector3 vector2 = new NVector3(sfloat.MinValue);
	
	        for (int i = 0; i < points.Length; i++)
	        {
	            NVector3.Min(vector3, points[i], out vector3);
	            NVector3.Max(vector2, points[i], out vector2);
	        }
	
	        return new JBBox(vector3, vector2);
	    }
	
	    public readonly ContainmentType Contains(in JBBox box)
	    {
	        ContainmentType result = ContainmentType.Disjoint;
	        if (Max.X >= box.Min.X && Min.X <= box.Max.X && Max.Y >= box.Min.Y && Min.Y <= box.Max.Y &&
	            Max.Z >= box.Min.Z && Min.Z <= box.Max.Z)
	        {
	            result = Min.X <= box.Min.X && box.Max.X <= Max.X && Min.Y <= box.Min.Y && box.Max.Y <= Max.Y &&
	                     Min.Z <= box.Min.Z && box.Max.Z <= Max.Z
	                ? ContainmentType.Contains
	                : ContainmentType.Intersects;
	        }
	
	        return result;
	    }
	
	    public readonly bool NotDisjoint(in JBBox box)
	    {
	        return Max.X >= box.Min.X && Min.X <= box.Max.X && Max.Y >= box.Min.Y && Min.Y <= box.Max.Y &&
	               Max.Z >= box.Min.Z && Min.Z <= box.Max.Z;
	    }
	
	    public readonly bool Disjoint(in JBBox box)
	    {
	        return !(Max.X >= box.Min.X && Min.X <= box.Max.X && Max.Y >= box.Min.Y && Min.Y <= box.Max.Y &&
	                 Max.Z >= box.Min.Z && Min.Z <= box.Max.Z);
	    }
	
	    public static JBBox CreateMerged(in JBBox original, in JBBox additional)
	    {
	        CreateMerged(original, additional, out JBBox result);
	        return result;
	    }
	
	    public static void CreateMerged(in JBBox original, in JBBox additional, out JBBox result)
	    {
	        NVector3.Min(original.Min, additional.Min, out result.Min);
	        NVector3.Max(original.Max, additional.Max, out result.Max);
	    }
	
	    public readonly NVector3 Center => (Min + Max) * ((sfloat)1.0f / (sfloat)2.0f);
	
	    public sfloat GetVolume()
	    {
	        NVector3 len = Max - Min;
	        return len.X * len.Y * len.Z;
	    }
	
	    public sfloat GetSurfaceArea()
	    {
	        NVector3 len = Max - Min;
	        return (sfloat)2.0f * (len.X * len.Y + len.Y * len.Z + len.Z * len.X);
	    }
	}
}
